﻿/* 
 * Copyright 2010 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections;
using Ngilead.Core.Exception;
using Ngilead.Core.Utils;
using log4net;
using Iesi.Collections.Generic;
using Iesi.Collections;

namespace Ngilead.Core.Replicator
{
    /// <summary>
    /// Bean transformer class
    /// </summary>
    public class BeanTransformer : IBeanTransformer
    {
        #region private
        /// <summary> Logger channel</summary>
        private static ILog logger = LogManager.GetLogger(typeof(CollectionReplicator));

        /// <summary>
        /// Cloned map (circual dependency management)
        /// </summary>
        private Dictionary<Object, Object> clonedMap;

        /// <summary>
        /// Stack of parent beans 
        /// </summary>
        private List<object> parentStack;

        /// <summary>
        /// The current proxy informations to use (merge operation)
        /// </summary>
        private Dictionary<string, object> currentProxyInformations;

        /// <summary>
        /// Replicator for arrays.
        /// </summary>
        private ArrayReplicator arrayReplicator;

        /// <summary>
        /// Replicator for collections.
        /// </summary>
        private CollectionReplicator collectionReplicator;

        /// <summary>
        /// Replicator for sets.
        /// </summary>
        private SetReplicator setReplicator;

        /// <summary>
        /// Replicator for dictionary.
        /// </summary>
        private DictionaryReplicator dictionaryReplicator;

        /// <summary>
        /// Replicator for instance.
        /// </summary>
        private InstanceReplicator instanceReplicator;

        /// <summary>
        /// The associated property filter
        /// </summary>
        private IPropertyFilter propertyFilter;

        #endregion

        /*----------------------------------------------------------------------------------------*/
        #region Properties

        /// <summary>
        ///  Property for proxy informations.
        /// </summary>
        public Dictionary<string, object> CurrentProxyInformations
        {
            get { return currentProxyInformations; }
            set { currentProxyInformations = value; }
        }

        /// <summary>
        /// Property for array replicator
        /// </summary>
        public ArrayReplicator ArrayReplicator
        {
            get { return arrayReplicator; }
            set
            {
                arrayReplicator = value;
                arrayReplicator.BeanTransformer = this;
            }
        }

        /// <summary>
        /// Property for collection replicator
        /// </summary>
        public CollectionReplicator CollectionReplicator
        {
            get { return collectionReplicator; }
            set
            {
                collectionReplicator = value;
                collectionReplicator.BeanTransformer = this;
            }
        }

        /// <summary>
        /// Property for set replicator
        /// </summary>
        public SetReplicator SetReplicator
        {
            get { return setReplicator; }
            set
            {
                setReplicator = value;
                setReplicator.BeanTransformer = this;
            }
        }

        /// <summary>
        /// Property for Dictionary replicator
        /// </summary>
        public DictionaryReplicator DictionaryReplicator
        {
            get { return dictionaryReplicator; }
            set
            {
                dictionaryReplicator = value;
                dictionaryReplicator.BeanTransformer = this;
            }
        }

        /// <summary>
        /// Instance replicator
        /// </summary>
        public InstanceReplicator InstanceReplicator
        {
            get { return instanceReplicator; }
            set
            {
                instanceReplicator = value;
                instanceReplicator.BeanTransformer = this;
            }
        }

        /// <summary>
        /// Property for filter
        /// </summary>
        public IPropertyFilter PropertyFilter
        {
            get { return propertyFilter; }
            set { propertyFilter = value; }
        }

        #endregion
        /*----------------------------------------------------------------------------------------*/

        #region ctor
        /// <summary>
        /// Empty constructor
        /// </summary>
        public BeanTransformer()
        {
            clonedMap = new Dictionary<object, object>();
            parentStack = new List<object>();

            // Default replicators
            ArrayReplicator = new ArrayReplicator();
            SetReplicator = new SetReplicator();
            CollectionReplicator = new CollectionReplicator();
            DictionaryReplicator = new DictionaryReplicator();
            InstanceReplicator = new InstanceReplicator();
        }

        #endregion

        #region IBeanTransformer Membres

        /// <summary>
        /// Indicates if the argument property from the 'fromBean' must be cloned to 'toBean'
        /// </summary>
        /// <param name="propertyName">Name of the property to clone</param>
        /// <param name="fromBean">The source object</param>
        /// <param name="toBean">The cloned object</param>
        /// <returns>true if the property must be cloned, false otherwise</returns>
        public bool ShouldPopulate(string propertyName,
                                   object fromBean,
                                   object toBean)
        {
            if (this.propertyFilter == null)
                return true;
            return propertyFilter.ShouldPopulate(propertyName, fromBean, toBean);
        }

        /// <summary>
        /// Get the CloneMap
        /// </summary>
        /// <returns>CloneMap</returns>
        public Dictionary<object, object> GetClonedMap()
        {
            return clonedMap;
        }

        /// <summary>
        /// Check if the CloneMap contains the target clone
        /// </summary>
        /// <param name="from">Object to clone</param>
        /// <returns>true/false</returns>
        protected bool ContainsTargetCloned(Object from)
        {
            return GetClonedMap().Keys.Contains(from);
        }

        /// <summary>
        /// Get the target clone from the CloneMap
        /// </summary>
        /// <param name="from">Object to clone</param>
        /// <returns>Clone</returns>
        protected Object GetTargetCloned(object from)
        {
            return GetClonedMap()[from];
        }

        /// <summary>
        /// Replicate the given from object, recursively if necessary
        ///</summary>
        public T Replicate<T>(T from)
        {
            if (from == null)
            {
                if (logger.IsInfoEnabled)
                {
                    logger.Info("Replicate<T>(T from) : where from = null");
                }
                return default(T);
            }

            Type fromType = from.GetType();

            if (logger.IsInfoEnabled)
            {
                logger.Info(String.Format("Replicate<T>(T from) : where T is {0}", fromType.ToString()));
            }

            // ValueType and String
            if (from is ValueType || from is String)
            {
                if (logger.IsInfoEnabled)
                {
                    logger.Info("from is ValueType || String = true");
                }
                return from;
            }

            // Already cloned
            if (ContainsTargetCloned(from))
            {
                if (logger.IsInfoEnabled)
                {
                    logger.Info("ContainsTargetCloned(from) = true : already cloned");
                }
                T to = (T)GetClonedMap()[from];
                return to;

            }

            // Array
            if (fromType.IsArray)
            {
                if (logger.IsInfoEnabled)
                {
                    logger.Info("fromType.IsArray = true");
                }
                return (T)ArrayReplicator.ReplicateArray((IList)from);
            }
            // IDictionary
            else if (fromType.GetInterfaces().Contains(typeof(IDictionary)))
            {
                if (fromType.IsGenericType)
                {
                    if (logger.IsInfoEnabled)
                    {
                        logger.Info("fromType.IsGenericIDictionary = true");
                    }
                    return (T)DictionaryReplicator.ReplicateDictionary((IDictionary)from);
                }
            }
            // IList
            else if (fromType.GetInterfaces().Contains(typeof(IList)))
            {
                if (fromType.IsGenericType)
                {
                    if (logger.IsInfoEnabled)
                    {
                        logger.Info("fromType.IsGenericIList = true");
                    }
                    return (T)CollectionReplicator.ReplicateIList((IList)from);
                }
            }
            // ISet
            else if (fromType.GetInterfaces().Contains(typeof(ISet)))
            {
                if (fromType.IsGenericType)
                {
                    if (logger.IsInfoEnabled)
                    {
                        logger.Info("fromType.IsGenericIList = true");
                    }
                    //return (T)CollectionReplicator.ReplicateCollection((ICollection)from);
                    return (T)SetReplicator.ReplicateSet((ISet)from);
                }
            }

            // Other objects
            return ReplicateInstance(from);
        }

        /// <summary>
        /// Replicate instance
        /// </summary>
        /// <typeparam name="T">Type</typeparam>
        /// <param name="from">Object</param>
        /// <returns>Clone</returns>
        protected T ReplicateInstance<T>(T from)
        {
            if (logger.IsInfoEnabled)
            {
                logger.Info(String.Format("ReplicateInstance<T>(T from) : where T is {0}", typeof(T).ToString()));
            }
            parentStack.Add(from);
            T objRet = InstanceReplicator.ReplicateInstance(from);
            parentStack.Remove(from);
            return objRet;
        }

        /// <summary>
        /// Return the parent of the current cloned entity.
        /// </summary>
        /// <returns></returns>
        public object GetCurrentParent()
        {
            if (parentStack.Count > 0)
            {
                return parentStack.Last<Object>();
            }
            else
            {
                return null;
            }
        }

        #endregion
    }
}
